﻿using System.Data;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq.Expressions;
using Df.Framework.Common;
using Df.Common.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Df.Business
{   
    public class BaseBusiness<TEntity> : IDisposable, IBaseBusiness<TEntity>
        where TEntity : BaseEntity
    {
        public BaseBusiness()
        {

        }
        private BaseDataContext _dataContext;

        public virtual BaseDataContext DataContext
        {
            get
            {
                if (_dataContext == null)
                {
                    if (ServiceFactory.DataContextType != null)
                        _dataContext = ServiceFactory.Create(ServiceFactory.DataContextType) as BaseDataContext;
                    else
                        _dataContext = ServiceFactory.Get<BaseDataContext>();

                    var objectContext = (_dataContext as IObjectContextAdapter).ObjectContext;

                    // Sets the command timeout for all the commands
                    objectContext.CommandTimeout = 600;
                    //this.AllowSerialization = true;
                    //Disable ProxyCreationDisabled to prevent the "In order to serialize the parameter, add the type to the known types collection for the operation using ServiceKnownTypeAttribute" error
                }
                return _dataContext;
            }
        }

        public virtual bool AllowSerialization
        {
            get
            {
                //return ((IObjectContextAdapter) _DataContext)
                //.ObjectContext.ContextOptions.ProxyCreationEnabled = false;
                return _dataContext.Configuration.ProxyCreationEnabled;
            }
            set
            {
                _dataContext.Configuration.ProxyCreationEnabled = !value;
            }
        }

        public virtual TEntity Get(Expression<Func<TEntity, bool>> predicate)
        {
            if (predicate != null)
            {
                using (DataContext)
                {
                    return DataContext.Set<TEntity>().Where(predicate).SingleOrDefault();
                }
            }
            else
            {
                throw new ApplicationException("Predicate value must be passed to Get<T>.");
            }
        }

        //public virtual T Get<T>(Expression<Func<T, bool>> predicate) where T : BaseEntity
        //{
        //    if (predicate != null)
        //    {
        //        using (DataContext)
        //        {
        //            return DataContext.Set<T>().Where(predicate).SingleOrDefault();
        //        }
        //    }
        //    else
        //    {
        //        throw new ApplicationException("Predicate value must be passed to Get<T>.");
        //    }
        //}

        public virtual IQueryable<TEntity> GetList(Expression<Func<TEntity, bool>> predicate)
        {
            try
            {
                return DataContext.Set<TEntity>().Where(predicate);
            }
            catch (Exception e)
            {
                throw new DfDbException(e);
            }
        }

        public virtual IQueryable<TEntity> GetList<TKey>(Expression<Func<TEntity, bool>> predicate,
            Expression<Func<TEntity, TKey>> orderBy)
        {
            try
            {
                return GetList(predicate).OrderBy(orderBy);
            }
            catch (Exception e)
            {
                throw new DfDbException(e);
            }
        }

        public virtual IQueryable<TEntity> GetList<TKey>(Expression<Func<TEntity, TKey>> orderBy)
        {
            try
            {
                return GetList().OrderBy(orderBy);
            }
            catch (Exception e)
            {
                throw new DfDbException(e);
            }
        }

        public virtual IQueryable<TEntity> GetList()
        {
            try
            {
                return DataContext.Set<TEntity>();
            }
            catch (Exception e)
            {
                throw new DfDbException(e);
            }
        }

        public virtual OperationStatus Save()
        {
            OperationStatus opStatus = new OperationStatus { Status = true };

            try
            {
                opStatus.Status = DataContext.SaveChanges() > 0;
            }
            catch (Exception exp)
            {
                opStatus = OperationStatus.CreateFromException("Error saving " + typeof(TEntity) + ".", exp);
            }

            return opStatus;
        }

        public virtual OperationStatus Create(TEntity entity)
        {
            OperationStatus opStatus = new OperationStatus { Status = true };

            try
            {
                DataContext.Set<TEntity>().Add(entity);
                opStatus.Status = DataContext.SaveChanges() > 0;
            }
            catch (Exception exp)
            {
                opStatus = OperationStatus.CreateFromException("Error creating " + typeof(TEntity) + ".", exp);
            }

            return opStatus;
        }

        public virtual OperationStatus Delete(TEntity entity)
        {
            OperationStatus opStatus = new OperationStatus { Status = true };

            try
            {
                DataContext.Set<TEntity>().Remove(entity);
                opStatus.Status = DataContext.SaveChanges() > 0;
            }
            catch (Exception exp)
            {
                opStatus = OperationStatus.CreateFromException("Error creating " + typeof(TEntity) + ".", exp);
            }

            return opStatus;
        }

        public virtual OperationStatus Delete(int entityId)
        {
            OperationStatus opStatus = new OperationStatus { Status = true };

            try
            {
                var entity = DataContext.Set<TEntity>().Find(entityId);
                DataContext.Set<TEntity>().Remove(entity);
                opStatus.Status = DataContext.SaveChanges() > 0;
            }
            catch (Exception exp)
            {
                opStatus = OperationStatus.CreateFromException("Error creating " + typeof(TEntity) + ".", exp);
            }

            return opStatus;
        }

        //public virtual OperationStatus Update(TEntity entity)
        //{
        //    OperationStatus opStatus = new OperationStatus { Status = true };

        //    try
        //    {
        //        DataContext.Set<TEntity>().Attach(entity);
        //        opStatus.Status = DataContext.SaveChanges() > 0;
        //    }
        //    catch (Exception exp)
        //    {
        //        opStatus = OperationStatus.CreateFromException("Error updating " + typeof(TEntity) + ".", exp);
        //    }

        //    return opStatus;
        //}

        public virtual OperationStatus Update(TEntity entity)
        {
            OperationStatus opStatus = new OperationStatus { Status = true };
            if (entity == null)
            {
                throw new ArgumentException("Cannot add a null entity.");
            }

            var entry = DataContext.Entry<TEntity>(entity);

            if (entry.State == EntityState.Detached)
            {
                var set = DataContext.Set<TEntity>();
                TEntity attachedEntity = set.Local.SingleOrDefault(e => e.Id == entity.Id);  // You need to have access to key

                if (attachedEntity != null)
                {
                    var attachedEntry = DataContext.Entry(attachedEntity);
                    attachedEntry.CurrentValues.SetValues(entity);
                }
                else
                {
                    entry.State = EntityState.Modified; // This should attach entity
                }
                try
                {
                    opStatus.Status = DataContext.SaveChanges() > 0;

                }
                catch (Exception ex)
                {
                    opStatus = OperationStatus.CreateFromException("Error updating " + typeof(TEntity) + ".", ex);
                }
            }

            return opStatus;
        }

        public virtual OperationStatus UpdateProperty(int entityId, string propertyName, object propertyValue)
        {
            OperationStatus opStatus = new OperationStatus { Status = true };

            try
            {
                var entity = DataContext.Set<TEntity>().Find(entityId);
                DataContext.Entry(entity).Property(propertyName).CurrentValue = propertyValue;
                opStatus.Status = DataContext.SaveChanges() > 0;
            }
            catch (Exception exp)
            {
                opStatus = OperationStatus.CreateFromException("Error updating " + typeof(TEntity) + ".", exp);
            }

            return opStatus;
        }

        //public virtual OperationStatus Update<T>(T entity, params string[] propsToUpdate) where T : BaseEntity
        //{
        //    OperationStatus opStatus = new OperationStatus { Status = true };

        //    try
        //    {
        //        DataContext.Set<T>().Attach(entity);
        //        opStatus.Status = DataContext.SaveChanges() > 0;
        //    }
        //    catch (Exception exp)
        //    {
        //        opStatus = OperationStatus.CreateFromException("Error updating " + typeof(T) + ".", exp);
        //    }

        //    return opStatus;
        //}

        public OperationStatus ExecuteStoreCommand(string cmdText, params object[] parameters)
        {
            var opStatus = new OperationStatus { Status = true };

            try
            {
                //opStatus.RecordsAffected = DataContext.ExecuteStoreCommand(cmdText, parameters);
                //TODO: [Papa] = Have not tested this yet.
                opStatus.RecordsAffected = DataContext.Database.ExecuteSqlCommand(cmdText, parameters);
            }
            catch (Exception exp)
            {
                OperationStatus.CreateFromException("Error executing store command: ", exp);
            }
            return opStatus;
        }

        //public virtual OperationStatus Delete<T>(T entity) where T : class
        //{
        //    OperationStatus opStatus = new OperationStatus { Status = true };

        //    try
        //    {
        //        ObjectSet<T> objectSet = DataContext.CreateObjectSet<T>();
        //        objectSet.Attach(entity);
        //        objectSet.DeleteObject(entity);
        //        opStatus.Status = DataContext.SaveChanges() > 0;
        //    }
        //    catch (Exception exp)
        //    {
        //        return OperationStatus.CreateFromException("Error deleting " + typeof(T), exp);
        //    }

        //    return opStatus;
        //}

        public void Dispose()
        {
            //if (DataContext != null)
            //    DataContext.Dispose();
            if (_dataContext != null)
            {
                _dataContext.Dispose();
                _dataContext = null;
            }
        }
    }
}
